home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / bin / munchlist < prev    next >
Text File  |  2009-10-02  |  26KB  |  771 lines

  1. #! /bin/sh
  2. #
  3. # $Id: munchlist.X,v 1.53 1995/01/08 23:23:36 geoff Exp $
  4. #
  5. # Copyright 1987, 1988, 1989, 1992, 1993, Geoff Kuenning, Granada Hills, CA
  6. # All rights reserved.
  7. #
  8. # Redistribution and use in source and binary forms, with or without
  9. # modification, are permitted provided that the following conditions
  10. # are met:
  11. #
  12. # 1. Redistributions of source code must retain the above copyright
  13. #    notice, this list of conditions and the following disclaimer.
  14. # 2. Redistributions in binary form must reproduce the above copyright
  15. #    notice, this list of conditions and the following disclaimer in the
  16. #    documentation and/or other materials provided with the distribution.
  17. # 3. All modifications to the source code must be clearly marked as
  18. #    such.  Binary redistributions based on modified source code
  19. #    must be clearly marked as modified versions in the documentation
  20. #    and/or other materials provided with the distribution.
  21. # 4. All advertising materials mentioning features or use of this software
  22. #    must display the following acknowledgment:
  23. #      This product includes software developed by Geoff Kuenning and
  24. #      other unpaid contributors.
  25. # 5. The name of Geoff Kuenning may not be used to endorse or promote
  26. #    products derived from this software without specific prior
  27. #    written permission.
  28. #
  29. # THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
  30. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  31. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  32. # ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
  33. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  34. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  35. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  36. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  37. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  38. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  39. # SUCH DAMAGE.
  40. #
  41. #    Given a list of words for ispell, generate a reduced list
  42. #    in which all possible affixes have been collapsed.  The reduced
  43. #    list will match the same list as the original.
  44. #
  45. #    Usage:
  46. #
  47. #    munchlist [-l lang] [-c lang] [-s hashfile] [-D] [-w chars] [-v] \
  48. #      [file] ...
  49. #
  50. #    Options:
  51. #
  52. #    -l lang    Specifies the language table to be used.  The default
  53. #        is "$LIBDIR/default.aff".
  54. #    -c lang    Specifies "conversion" language table.  If this option is
  55. #        given, the input file(s) will be assumed to be described by
  56. #        this table, rather than the table given in the -l option.
  57. #        This may be used to convert between incompatible language
  58. #        tables.  (When in doubt, use this option -- it doesn't
  59. #        hurt, and it may save you from creating a dictionary that has
  60. #        illegal words in it).  The default is no conversion.
  61. #    -T suff Specifies that the source word lists are in the format
  62. #        of a "suff"-suffixed file, rather than in the
  63. #        canonical form.  For example, "-T tex" specifies that
  64. #        string characters in the word lists are in TeX format.
  65. #        The string character conversions are taken from the language
  66. #        table specified by the "-l" switch.
  67. #    -s    Remove any words that are already covered by the
  68. #        dictionary in 'hashfile'.  The words will be removed
  69. #        only if all affixes are covered.  This option should not be
  70. #        specified when the main dictionary is being munched.
  71. #        'Hashfile' must have been created with the language
  72. #        table given in the -l option, but this is not checked.
  73. #    -D    Leave temporary files for debugging purposes
  74. #    -w    Passed on to ispell (specify chars that are part of a word)
  75. #        Unfortunately, special characters must be quoted twice
  76. #        rather than once when invoking this script.  Also, since
  77. #        buildhash doesn't accept this option, the final ispell -l
  78. #        step ignores it, making it somewhat less than useful.
  79. #    -v    Report progress to stderr.
  80. #
  81. #    The given input files are merged, then processed by 'ispell -c'
  82. #    to generate possible affix lists;  these are then combined
  83. #    and reduced.  The final result is written to standard output.
  84. #
  85. #    For portability to older systems, I have avoided getopt.
  86. #
  87. #        Geoff Kuenning
  88. #        2/28/87
  89. #
  90. # $Log: munchlist.X,v $
  91. # Revision 1.53  1995/01/08  23:23:36  geoff
  92. # Support variable hashfile suffixes for DOS purposes.
  93. #
  94. # Revision 1.52  1994/12/27  23:08:46  geoff
  95. # Dynamically determine how to pass backslashes to 'tr' so that it'll
  96. # work on any machine.  Define LC_CTYPE to work around yet more
  97. # internationalized sort programs.  Work around a bug in GNU uniq that
  98. # uses the wrong separator between counts and duplicated lines.
  99. #
  100. # Revision 1.51  1994/11/21  07:02:54  geoff
  101. # Correctly quote the arguments to 'tr' when detecting systems with
  102. # unsigned sorts.  Be sure to provide a zero exit status on all systems,
  103. # even if MUNCHDEBUG is not set.
  104. #
  105. # Revision 1.50  1994/10/25  05:46:05  geoff
  106. # Export values for LANG and LOCALE in an attempt to override some
  107. # stupidly-internationalized sort programs.
  108. #
  109. # Revision 1.49  1994/10/04  03:51:30  geoff
  110. # Add the MUNCHMAIL feature.  If the MUNCHMAIL environment variable is
  111. # set to an email address, debugging information about the munchlist run
  112. # will automatically be collected and mailed to that address.
  113. #
  114. # Revision 1.48  1994/05/17  06:32:06  geoff
  115. # Don't look for affix tables in LIBDIR if the name contains a slash
  116. #
  117. # Revision 1.47  1994/04/27  02:50:48  geoff
  118. # Fix some cosmetic flaws in the verbose-mode messages.
  119. #
  120. # Revision 1.46  1994/01/25  07:11:59  geoff
  121. # Get rid of all old RCS log lines in preparation for the 3.1 release.
  122. #
  123. #
  124. if [ "X$MUNCHMAIL" != X ]
  125. then
  126.     TMPMUNCHMAIL=`mktemp -q ${TMPDIR-/tmp}/munchlist.XXXXXX.mail`
  127.     if [ $? -ne 0 ]; then
  128.          echo "$0: Can't create temp file, exiting..."
  129.          exit 1
  130.     fi
  131.     exec 2> ${TMPMUNCHMAIL}
  132.     echo "munchlist $*" 1>&2
  133.     set -vx
  134. fi
  135. LIBDIR=/usr/lib/ispell
  136. TDIR="${TMPDIR-/tmp}/munchlist.$$.d"
  137. mkdir ${TDIR}
  138.     if [ $? -ne 0 ]; then
  139.          echo "$0: Can't create temp dir, exiting..."
  140.          exit 1
  141.     fi
  142. TMP=${TDIR}/munch$$
  143. SORTTMP="-T ${TDIR}"            # !!SORTTMP!!
  144. if [ -r ./icombine ]
  145. then
  146.     COMBINE=./icombine
  147. else
  148.     COMBINE=icombine
  149. fi
  150. if [ -r ./ijoin ]
  151. then
  152.     JOIN=./ijoin
  153. else
  154.     JOIN=ijoin
  155. fi
  156.  
  157. #
  158. # The following is necessary so that some internationalized versions of
  159. # sort(1) don't confuse things by sorting into a nonstandard order.
  160. #
  161. LANG=C
  162. LOCALE=C
  163. LC_CTYPE=C
  164. export LANG LOCALE LC_CTYPE
  165.  
  166. debug=no
  167. dictopt=
  168. langtabs=${LIBDIR}/default.aff
  169. convtabs=
  170. strip=no
  171. icflags=
  172. verbose=false
  173. # The following value of "wchars" is necessary to prevent ispell from
  174. # receiving a null argument if -w is not specified.  As long as "A" is
  175. # a member of the existing character set, ispell will ignore the argument.
  176. wchars=-wA
  177. while [ $# != 0 ]
  178. do
  179.     case "$1" in
  180.     -l)
  181.         case "$2" in
  182.         */*)
  183.             langtabs=$2
  184.             ;;
  185.         *)
  186.             if [ -r "$2" ]
  187.             then
  188.             langtabs="$2"
  189.             else
  190.             langtabs="${LIBDIR}/$2"
  191.             fi
  192.             ;;
  193.         esac
  194.         if [ ! -r "$langtabs" ]
  195.         then
  196.         echo "Can't open language table '$2'" 1>&2
  197.         exit 1
  198.         fi
  199.         shift
  200.         ;;
  201.     -c)
  202.         if [ -r "$2" ]
  203.         then
  204.         convtabs="$2"
  205.         elif [ -r "${LIBDIR}/$2" ]
  206.         then
  207.         convtabs="${LIBDIR}/$2"
  208.         else
  209.         echo "Can't open conversion language table '$2'" 1>&2
  210.         exit 1
  211.         fi
  212.         shift
  213.         ;;
  214.     -s)
  215.         dictopt="-d $2"
  216.         strip=yes
  217.         shift
  218.         ;;
  219.     -D)
  220.         debug=yes
  221.         ;;
  222.     -T)
  223.         icflags="-T $2"
  224.         shift
  225.         ;;
  226.     -v)
  227.         verbose=true
  228.         ;;
  229.     -w)
  230.         wchars="-w$2"
  231.         shift
  232.         ;;
  233.     --)
  234.         shift
  235.         break
  236.         ;;
  237.     -)
  238.         break
  239.         ;;
  240.     -*)
  241.         echo 'Usage: munchlist [-l lang] [-c lang] [-T suff] [-s hashfile] [-D] [-w chars] [-v] [file] ...' \
  242.           1>&2
  243.         exit 2
  244.         ;;
  245.     *)
  246.         break
  247.         ;;
  248.     esac
  249.     shift
  250. done
  251. if [ "X$MUNCHMAIL" != X ]
  252. then
  253.     verbose=true
  254.     debug=yes
  255. fi
  256. trap "/bin/rm -rf ${TDIR}; exit 1" 1 2 13 15
  257. #
  258. # Names of temporary files.  This is just to make the code a little easier
  259. # to read.
  260. #
  261. EXPANDEDINPUT=${TMP}a
  262. STRIPPEDINPUT=${TMP}b
  263. CRUNCHEDINPUT=${TMP}c
  264. PRODUCTLIST=${TMP}d
  265. EXPANDEDPAIRS=${TMP}e
  266. LEGALFLAGLIST=${TMP}f
  267. JOINEDPAIRS=${TMP}g
  268. MINIMALAFFIXES=${TMP}h
  269. CROSSROOTS=${TMP}i
  270. CROSSEXPANDED=${TMP}j
  271. CROSSPAIRS=${TMP}k
  272. CROSSILLEGAL=${TMP}l
  273. ILLEGALCOMBOS=${TMP}m
  274. FAKEDICT=${TMP}n
  275. # Ispell insists that hash files have a ".hash" suffix
  276. FAKEHASH=${TMP}o.hash
  277. AWKSCRIPT=${TMP}p
  278. if [ "$debug" = yes ]
  279. then
  280.     touch $EXPANDEDINPUT $STRIPPEDINPUT $CRUNCHEDINPUT $PRODUCTLIST \
  281.       $EXPANDEDPAIRS $LEGALFLAGLIST $JOINEDPAIRS $MINIMALAFFIXES \
  282.       $CROSSROOTS $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL $ILLEGALCOMBOS \
  283.       $FAKEDICT $FAKEHASH $AWKSCRIPT
  284.     rm -f ${TDIR}/EXPANDEDINPUT ${TDIR}/STRIPPEDINPUT ${TDIR}/CRUNCHEDINPUT \
  285.       ${TDIR}/PRODUCTLIST ${TDIR}/EXPANDEDPAIRS ${TDIR}/LEGALFLAGLIST \
  286.       ${TDIR}/JOINEDPAIRS ${TDIR}/MINIMALAFFIXES ${TDIR}/CROSSROOTS \
  287.       ${TDIR}/CROSSEXPANDED ${TDIR}/CROSSPAIRS ${TDIR}/CROSSILLEGAL \
  288.       ${TDIR}/ILLEGALCOMBOS ${TDIR}/FAKEDICT ${TDIR}/FAKEHASH.hash \
  289.       ${TDIR}/AWKSCRIPT ${TDIR}/CROSSROOTS.[0-9]* ${TDIR}/CROSSEXP.[0-9]* \
  290.       ${TDIR}/CROSSPAIRS.[0-9]* ${TDIR}/CROSSILLEGAL.[0-9]*
  291.     ln $EXPANDEDINPUT ${TDIR}/EXPANDEDINPUT
  292.     ln $STRIPPEDINPUT ${TDIR}/STRIPPEDINPUT
  293.     ln $CRUNCHEDINPUT ${TDIR}/CRUNCHEDINPUT
  294.     ln $PRODUCTLIST ${TDIR}/PRODUCTLIST
  295.     ln $EXPANDEDPAIRS ${TDIR}/EXPANDEDPAIRS
  296.     ln $LEGALFLAGLIST ${TDIR}/LEGALFLAGLIST
  297.     ln $JOINEDPAIRS ${TDIR}/JOINEDPAIRS
  298.     ln $MINIMALAFFIXES ${TDIR}/MINIMALAFFIXES
  299.     ln $CROSSROOTS ${TDIR}/CROSSROOTS
  300.     ln $CROSSEXPANDED ${TDIR}/CROSSEXPANDED
  301.     ln $CROSSPAIRS ${TDIR}/CROSSPAIRS
  302.     ln $CROSSILLEGAL ${TDIR}/CROSSILLEGAL
  303.     ln $ILLEGALCOMBOS ${TDIR}/ILLEGALCOMBOS
  304.     ln $FAKEDICT ${TDIR}/FAKEDICT
  305.     ln $FAKEHASH ${TDIR}/FAKEHASH.hash
  306.     ln $AWKSCRIPT ${TDIR}/AWKSCRIPT
  307. fi
  308. #
  309. # Create a dummy dictionary to hold a compiled copy of the language
  310. # table.  Initially, it holds the conversion table, if it exists.
  311. #
  312. case "X$convtabs" in
  313.     X)
  314.     convtabs="$langtabs"
  315.     ;;
  316. esac
  317. echo 'QQQQQQQQ' > $FAKEDICT
  318. buildhash -s $FAKEDICT $convtabs $FAKEHASH \
  319.   ||  (echo "Couldn't create fake hash file" 1>&2; /bin/rm -rf ${TDIR}; exit 1) \
  320.   ||  exit 1
  321. #
  322. # Figure out how 'sort' sorts signed fields, for arguments to ijoin.
  323. # This is a little bit of a tricky pipe, but the result is that SIGNED
  324. # is set to "-s" if characters with the top bit set sort before those
  325. # without, and "-u" if the reverse is true.  How does it work?  The
  326. # first "tr" step generates two lines, one containing "-u", the other
  327. # with the same but with the high-order bit set.  The second "tr"
  328. # changesthe high-bit "-u" back to "-s".  If the high-bit "-u" was
  329. # sorted first, the sed step will select "-s" for SIGNED; otherwise
  330. # it'll pick "-u".  We have to be careful about backslash quoting
  331. # conventions, because some systems differ.
  332. #
  333. backslash=\\
  334. for i in 0 1 2 3
  335. do
  336.     if [ `echo a | tr "${backslash}141" b` = b ]
  337.     then
  338.     break
  339.     fi
  340.     backslash="$backslash$backslash"
  341. done
  342. SIGNED=`echo '-s
  343. -u' | tr s "${backslash}365" | sort | tr "${backslash}365" s | sed 1q`
  344. #
  345. # Collect all the input and expand all the affix options (ispell -e),
  346. # and preserve (sorted) for later joining in EXPANDEDINPUT.  The icombine
  347. # step is to make sure that unneeded capitalizations (e.g., Farmer and farmer)
  348. # are weeded out.  The first sort must be folded for icombine;  the second
  349. # must be unfolded for join.
  350. #
  351. $verbose  &&  echo "Collecting input." 1>&2
  352. if [ $# -eq 0 ]
  353. then
  354.     ispell "$wchars" -e1 -d $FAKEHASH -p /dev/null | tr " " '
  355. '
  356. else
  357.     cat "$@" | ispell "$wchars" -e1 -d $FAKEHASH -p /dev/null | tr " " '
  358. '
  359. fi \
  360.   | sort $SORTTMP -u -k1f,1 -k1 \
  361.   | $COMBINE $icflags $langtabs \
  362.   | sort $SORTTMP -u > $EXPANDEDINPUT
  363. #
  364. # If a conversion table existed, recreate the fake hash file with the
  365. # "real" language table.
  366. #
  367. case "$convtabs" in
  368.     $langtabs)
  369.     ;;
  370.     *)
  371.     buildhash -s $FAKEDICT $langtabs $FAKEHASH \
  372.       ||  (echo "Couldn't create fake hash file" 1>&2; \
  373.         /bin/rm -rf ${TDIR}; exit 1) \
  374.       ||  exit 1
  375.     ;;
  376. esac
  377. /bin/rm -f ${FAKEDICT}*
  378. #
  379. # If the -s (strip) option was specified, remove all
  380. # expanded words that are covered by the dictionary.  This produces
  381. # the final list of expanded words that this dictionary must cover.
  382. # Leave the list in STRIPPEDINPUT.
  383. #
  384. if [ "X$strip" = "Xno" ]
  385. then
  386.     rm -f $STRIPPEDINPUT
  387.     ln $EXPANDEDINPUT $STRIPPEDINPUT
  388.     if [ "$debug" = yes ]
  389.     then
  390.     rm -f ${TDIR}/STRIPPEDINPUT
  391.     ln $STRIPPEDINPUT ${TDIR}/STRIPPEDINPUT
  392.     fi
  393. else
  394.     $verbose  &&  echo "Stripping words already in the dictionary." 1>&2
  395.     ispell "$wchars" -l $dictopt -p /dev/null < $EXPANDEDINPUT \
  396.       > $STRIPPEDINPUT
  397. fi
  398. #
  399. # Figure out what the flag-marking character is.
  400. #
  401. $verbose  &&  echo "Finding flag marker." 1>&2
  402. flagmarker=`ispell -D -d $FAKEHASH \
  403.   | sed -n '/^flagmarker/s/flagmarker //p'`
  404. case "$flagmarker" in
  405.     \\*)
  406.     flagmarker=`expr "$flagmarker" : '.\(.\)'`
  407.     ;;
  408. esac    
  409. #
  410. # Munch the input to generate roots and affixes (ispell -c).  We are
  411. # only interested in words that have at least one affix (egrep $flagmarker);
  412. # the next step will pick up the rest.  Some of the roots are illegal.  We
  413. # use join to restrict the output to those root words that are found
  414. # in the original dictionary.
  415. #
  416. $verbose  &&  echo "Generating roots and affixes." 1>&2
  417. ispell "$wchars" -c -W0 -d $FAKEHASH -p /dev/null < $STRIPPEDINPUT \
  418.   | tr " " '
  419. ' \
  420.   | egrep "$flagmarker" | sort $SORTTMP -u "-t$flagmarker" -k1,1 -k2 \
  421.   | $JOIN $SIGNED "-t$flagmarker" - $EXPANDEDINPUT > $CRUNCHEDINPUT
  422. #
  423. # We now have a list of legal roots, and of affixes that apply to the
  424. # root words.  However, it is possible for some affix flags to generate more
  425. # than one output word.  For example, with the flag table entry
  426. #
  427. #    flag R:    . > ER
  428. #        . > ERS
  429. #
  430. # the input "BOTHER" will generate an entry "BOTH/R" in CRUNCHEDINPUT.  But
  431. # this will accept "BOTHER" and "BOTHERS" in the dictionary, which is
  432. # wrong (in this case, though it's good English).
  433. #
  434. # To cure this problem, we first have to know which flags generate which
  435. # expansions.  We use ispell -e3 to expand the flags (the second e causes
  436. # the root and flag to be included in the output), and get pairs
  437. # suitable for joining.  In the example above, we would get
  438. #
  439. #    BOTH/R BOTHER
  440. #    BOTH/R BOTHERS
  441. #
  442. # We save this in EXPANDEDPAIRS for the next step.
  443. #
  444. $verbose  &&  echo 'Expanding dictionary into EXPANDEDPAIRS.' 1>&2
  445. ispell "$wchars" -e3 -d $FAKEHASH -p /dev/null < $CRUNCHEDINPUT \
  446.   | sort $SORTTMP -k2 > $EXPANDEDPAIRS
  447. #
  448. # Now we want to extract the lines in EXPANDEDPAIRS in which the second field
  449. # is *not* listed in the original dictionary EXPANDEDINPUT;  these illegal
  450. # lines contain the flags we cannot include without accepting illegal words.
  451. # It is somewhat easier to extract those which actually are listed (with
  452. # join), and then use comm to strip these from EXPANDEDPAIRS to get the
  453. # illegal expansions, together with the flags that generate them (we must
  454. # re-sort EXPANDEDPAIRS before running comm).  Sed
  455. # gets rid of the expansion and uniq gets rid of duplicates.  Comm then
  456. # selects the remainder of the list from CRUNCHEDINPUT and puts it in
  457. # LEGALFLAGLIST.  The final step is to use a sort and icombine to put
  458. # the list into a one-entry-per-root format.
  459. #
  460. # BTW, I thought of using cut for the sed step (on systems that have it),
  461. # but it turns out that sed is faster!
  462. #
  463. $JOIN -j1 2 -o 1.1 1.2 $SIGNED $EXPANDEDPAIRS $EXPANDEDINPUT \
  464.   | sort $SORTTMP -u > $JOINEDPAIRS
  465.  
  466. sort $SORTTMP -o $EXPANDEDPAIRS $EXPANDEDPAIRS
  467. sort $SORTTMP -o $CRUNCHEDINPUT $CRUNCHEDINPUT
  468.  
  469. $verbose  &&  echo 'Creating list of legal roots/flags.' 1>&2
  470. comm -13 $JOINEDPAIRS $EXPANDEDPAIRS \
  471.   | (sed -e 's; .*$;;' ; /bin/rm -f $JOINEDPAIRS $EXPANDEDPAIRS) \
  472.   | uniq \
  473.   | (comm -13 - $CRUNCHEDINPUT ; /bin/rm -f $CRUNCHEDINPUT) \
  474.   | sort $SORTTMP -u "-t$flagmarker" -k1f,1 -k1 \
  475.   | $COMBINE $langtabs > $LEGALFLAGLIST
  476.  
  477. #
  478. # LEGALFLAGLIST now contains root/flag combinations that, when expanded,
  479. # produce only words from EXPANDEDPAIRS.  However, there is still a
  480. # problem if the language tables have any cross-product flags.  A legal
  481. # root may appear in LEGALFLAGLIST with two flags that participate
  482. # in cross-products.  When such a dictionary entry is expanded,
  483. # the cross-products will generate some extra words that may not
  484. # be in EXPANDEDPAIRS.  We need to remove these from LEGALFLAGLIST.
  485. #
  486. # The first step is to collect the names of the flags that participate
  487. # in cross-products.  Ispell will dump the language tables for us, and
  488. # sed is a pretty handy way to strip out extra information.  We use
  489. # uniq -c and a numerical sort to put the flags in approximate order of how
  490. # "productive" they are (in terms of how likely they are to generate a lot
  491. # of output words).  The least-productive flags are given last and will
  492. # be removed first.
  493. #
  494. $verbose \
  495.   &&  echo 'Creating list of flags that participate in cross-products.' 1>&2
  496. ispell -D -d $FAKEHASH \
  497.   | sed -n '1,$s/:.*$//
  498.     /^flagmarker/d
  499.     /^prefixes/,/^suffixes/s/^  flag \*/p /p
  500.     /^suffixes/,$s/^  flag \*/s /p' \
  501.   | sort $SORTTMP \
  502.   | uniq -c \
  503.   | tr '    ' ' ' \
  504.   | sort $SORTTMP -k1rn,1 -k3 > $PRODUCTLIST
  505.  
  506. if [ `egrep ' p ' $PRODUCTLIST | wc -l` -gt 0 \
  507.   -a `egrep ' s ' $PRODUCTLIST | wc -l` -gt 0 ]
  508. then
  509.     #
  510.     # The language tables allow cross products.  See if LEGALFLAGLIST has
  511.     # any roots with multiple cross-product flags.  Put them in CROSSROOTS.
  512.     #
  513.     $verbose  &&  echo 'Finding prefix and suffix flags.' 1>&2
  514.     preflags=`sed -n 's/^[ 0-9]*p //p' $PRODUCTLIST | tr -d '
  515. '`
  516.     sufflags=`sed -n 's/^[ 0-9]*s //p' $PRODUCTLIST | tr -d '
  517. '`
  518.     egrep "$flagmarker.*[$preflags].*[$sufflags]|$flagmarker.*[$sufflags].*[$preflags]" \
  519.       $LEGALFLAGLIST \
  520.       > $CROSSROOTS
  521.  
  522.     #
  523.     # We will need an awk script;  it's so big that it core-dumps my shell
  524.     # under certain conditions.  The rationale behind the script is commented
  525.     # where the script is used.  Note that you may want to change this
  526.     # script for languages other than English.
  527.     #
  528.     case "$flagmarker" in
  529.     /)
  530.         sedchar=:
  531.         ;;
  532.     *)
  533.         sedchar=/
  534.         ;;
  535.     esac
  536.     $verbose  &&  echo 'Creating awk script.' 1>&2
  537.     sed -e "s/PREFLAGS/$preflags/" -e "s/SUFFLAGS/$sufflags/" \
  538.       -e "s;ILLEGALCOMBOS;$ILLEGALCOMBOS;" \
  539.       -e "s${sedchar}FLAGMARKER${sedchar}$flagmarker${sedchar}" \
  540.       > $AWKSCRIPT << 'ENDOFAWKSCRIPT'
  541.     BEGIN \
  542.         {
  543.         preflags = "PREFLAGS"
  544.         sufflags = "SUFFLAGS"
  545.         illegalcombos = "ILLEGALCOMBOS"
  546.         flagmarker = "FLAGMARKER"
  547.         pflaglen = length (preflags)
  548.         for (i = 1;  i <= pflaglen;  i++)
  549.         pflags[i] = substr (preflags, i, 1);
  550.         sflaglen = length (sufflags)
  551.         for (i = 1;  i <= sflaglen;  i++)
  552.         sflags[i] = substr (sufflags, i, 1);
  553.         }
  554.         {
  555.         len = length ($2)
  556.         pnew2 = ""
  557.         snew2 = ""
  558.         pbad = ""
  559.         sbad = ""
  560.         sufs = 0
  561.         pres = 0
  562.         for (i = 1;  i <= len;  i++)
  563.         {
  564.         curflag = substr ($2, i, 1)
  565.         for (j = 1;  j <= pflaglen;  j++)
  566.             {
  567.             if (pflags[j] == curflag)
  568.             {
  569.             pres++
  570.             pnew2 = substr ($2, 1, i - 1) substr ($2, i + 1)
  571.             pbad = curflag
  572.             }
  573.             }
  574.         for (j = 1;  j <= sflaglen;  j++)
  575.             {
  576.             if (sflags[j] == curflag)
  577.             {
  578.             sufs++
  579.             snew2 = substr ($2, 1, i - 1) substr ($2, i + 1)
  580.             sbad = curflag
  581.             }
  582.             }
  583.         }
  584.         if (pres == 1)
  585.         {
  586.         print $1 flagmarker pnew2
  587.         print $1 flagmarker pbad >> illegalcombos
  588.         }
  589.         else if (sufs == 1)
  590.         {
  591.         print $1 flagmarker snew2
  592.         print $1 flagmarker sbad >> illegalcombos
  593.         }
  594.         else if (pres > 0)
  595.         {
  596.         print $1 flagmarker pnew2
  597.         print $1 flagmarker pbad >> illegalcombos
  598.         }
  599.         else
  600.         {
  601.         print $1 flagmarker snew2
  602.         print $1 flagmarker sbad >> illegalcombos
  603.         }
  604.         }
  605. ENDOFAWKSCRIPT
  606.     : > $ILLEGALCOMBOS
  607.     dbnum=0
  608.     while [ -s $CROSSROOTS ]
  609.     do
  610.     #
  611.     # CROSSROOTS contains the roots whose cross-product expansions
  612.     # might be illegal.  We now need to locate the actual illegal ones.
  613.     # We do this in much the same way we created LEGALFLAGLIST from
  614.     # CRUNCHEDINPUT.  First we make CROSSEXPANDED, which is analogous
  615.     # to EXPANDEDPAIRS.
  616.     #
  617.     $verbose  &&  echo "Creating cross expansions (pass $dbnum)." 1>&2
  618.     ispell "$wchars" -e3 -d $FAKEHASH -p /dev/null < $CROSSROOTS \
  619.       | sort $SORTTMP -k2 > $CROSSEXPANDED
  620.     #
  621.     # Now we join CROSSEXPANDED against EXPANDEDINPUT to produce
  622.     # CROSSPAIRS, and then comm that against CROSSEXPANDED to
  623.     # get CROSSILLEGAL, the list of illegal cross-product flag
  624.     # combinations.
  625.     #
  626.     $JOIN -j1 2 -o 1.1 1.2 $SIGNED $CROSSEXPANDED $EXPANDEDINPUT \
  627.       | sort $SORTTMP -u > $CROSSPAIRS
  628.  
  629.     sort $SORTTMP -u -o $CROSSEXPANDED $CROSSEXPANDED
  630.  
  631.     $verbose \
  632.       &&  echo "Finding illegal cross expansions (pass $dbnum)." 1>&2
  633.     comm -13 $CROSSPAIRS $CROSSEXPANDED \
  634.       | sed -e 's; .*$;;' \
  635.       | uniq > $CROSSILLEGAL
  636.  
  637.     if [ "$debug" = yes ]
  638.     then
  639.         mv $CROSSROOTS $TDIR/CROSSROOTS.$dbnum
  640.         ln $CROSSEXPANDED $TDIR/CROSSEXP.$dbnum
  641.         ln $CROSSPAIRS $TDIR/CROSSPAIRS.$dbnum
  642.         ln $CROSSILLEGAL $TDIR/CROSSILLEGAL.$dbnum
  643.     fi
  644.     #
  645.     # Now it is time to try to clear up the illegalities.  For 
  646.     # each word in the illegal list, remove one of the cross-product
  647.     # flags.  The flag chosen is selected in an attempt to cure the
  648.     # problem quickly, as follows:  (1) if there is only one suffix
  649.     # flag or only one prefix flag, we remove that.  (2) If there is
  650.     # a prefix flag, we remove the "least desirable" (according to
  651.     # the order of preflags). (This may be pro-English prejudice,
  652.     # and you might want to change this if your language is prefix-heavy).
  653.     # (3) Otherwise we remove the least-desirable suffix flag
  654.     #
  655.     # The output of the awk script becomes the new CROSSROOTS.  In
  656.     # addition, we add the rejected flags to ILLEGALCOMBOS (this is done
  657.     # inside the awk script) so they can be removed from LEGALFLAGLIST
  658.     # later.
  659.     #
  660.     awk "-F$flagmarker" -f $AWKSCRIPT $CROSSILLEGAL > $CROSSROOTS
  661.     if [ "$debug" = yes ]
  662.     then
  663.         /bin/rm -f $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL
  664.     fi
  665.     dbnum=`expr $dbnum + 1`
  666.     done
  667.     /bin/rm -f $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL $AWKSCRIPT
  668.     #
  669.     # Now we have, in ILLEGALCOMBOS, a list of root/flag combinations
  670.     # that must be removed from LEGALFLAGLIST to get the final list
  671.     # of truly legal flags.  ILLEGALCOMBOS has one flag per line, so
  672.     # by turning LEGALFLAGLIST into this form (sed), it's an
  673.     # easy task for comm.  We have to recombine flags again after the
  674.     # extraction, to get all flags for a given root on the same line so that
  675.     # cross-products will come out right.
  676.     #
  677.     if [ -s $ILLEGALCOMBOS ]
  678.     then
  679.     sort $SORTTMP -u -o $ILLEGALCOMBOS $ILLEGALCOMBOS
  680.     $verbose  &&  echo 'Finding roots of cross expansions.' 1>&2
  681.     sort $SORTTMP $LEGALFLAGLIST \
  682.       | sed '/\/../{
  683.           s;^\(.*\)/\(.\)\(.*\);\1/\2\
  684. \1/\3;
  685.           P
  686.           D
  687.           }' \
  688.       | comm -23 - $ILLEGALCOMBOS \
  689.       | sort $SORTTMP -u "-t$flagmarker" -k1f,1 -k1 \
  690.       | $COMBINE $langtabs > $CROSSROOTS
  691.     mv $CROSSROOTS $LEGALFLAGLIST
  692.     if [ "$debug" = yes ]
  693.     then
  694.         rm -f ${TDIR}/LEGALFLAGLIST1
  695.         ln $LEGALFLAGLIST ${TDIR}/LEGALFLAGLIST1
  696.     fi
  697.     fi
  698. fi
  699. /bin/rm -f $PRODUCTLIST $CROSSROOTS $ILLEGALCOMBOS $EXPANDEDINPUT
  700. #
  701.  
  702. # We now have (in LEGALFLAGLIST) a list of roots and flags which will
  703. # accept words taken from EXPANDEDINPUT and no others (though some of
  704. # EXPANDEDINPUT is not covered by this list).  However, many of the
  705. # expanded words can be generated in more than one way.  For example,
  706. # "bather" can be generated from "bath/R" and "bathe/R".  This wastes
  707. # unnecessary space in the raw dictionary and, in some cases, in the
  708. # hash file as well.  The solution is to list the various ways of
  709. # getting a given word and choose exactly one.  All other things being
  710. # equal, we want to choose the one with the highest expansion length
  711. # to root length ratio.  The ispell -e4 option takes care of this by
  712. # providing us with a field to sort on.
  713. #
  714. # The ispell/awk combination is similar to the ispell/sed pipe used to
  715. # generate EXPANDEDPAIRS, except that ispell adds an extra field
  716. # giving the sort order.  The first sort gets things in order so the
  717. # first root listed is the one we want, and the second sort (-um) then
  718. # selects that first root.  Sed strips the expansion from the root,
  719. # and a final sort -u generates MINIMALAFFIXES, the final list of
  720. # affixes that (more or less) minimally covers what it can from
  721. # EXPANDEDINPUT.
  722. #
  723. $verbose  &&  echo 'Eliminating non-optimal affixes.' 1>&2
  724. ispell "$wchars" -e4 -d $FAKEHASH -p /dev/null < $LEGALFLAGLIST \
  725.   | sort $SORTTMP -k2,2 -k3rn,3 -k1,1 \
  726.   | sort $SORTTMP -um -k2,2 \
  727.   | sed -e 's; .*$;;' \
  728.   | sort $SORTTMP -u "-t$flagmarker" -k1f,1 -k1 > $MINIMALAFFIXES
  729. /bin/rm -f $LEGALFLAGLIST
  730. #
  731. # Now we're almost done.  MINIMALAFFIXES covers some (with luck, most)
  732. # of the words in STRIPPEDINPUT.  Now we must create a list of the remaining
  733. # words (those omitted by MINIMALAFFIXES) and add it to MINIMALAFFIXES.
  734. # The best way to do this is to actually build a partial dictionary from
  735. # MINIMALAFFIXES in FAKEHASH, and then use ispell -l to list the words that
  736. # are not covered by this dictionary.  This must then be combined with the
  737. # reduced version of MINIMALAFFIXES and sorted to produce the final result.
  738. #
  739. $verbose  &&  echo "Generating output word list." 1>&2
  740. if [ -s $MINIMALAFFIXES ]
  741. then
  742.     buildhash -s $MINIMALAFFIXES $langtabs $FAKEHASH > /dev/null \
  743.       ||  (echo "Couldn't create intermediate hash file" 1>&2;
  744.     /bin/rm -rf ${TDIR};
  745.     exit 1) \
  746.       ||  exit 1
  747.     if [ "$debug" = yes ]
  748.     then
  749.     rm -f ${TDIR}/MINAFFIXES..cnt \
  750.       ${TDIR}/MINAFFIXES.stat
  751.     ln $MINIMALAFFIXES..cnt ${TDIR}/MINAFFIXES..cnt
  752.     ln $MINIMALAFFIXES.stat ${TDIR}/MINAFFIXES.stat
  753.     fi
  754.     (ispell "$wchars" -l -d $FAKEHASH -p /dev/null < $STRIPPEDINPUT; \
  755.     $COMBINE $langtabs < $MINIMALAFFIXES) \
  756.       | sort $SORTTMP "-t$flagmarker" -u -k1f,1 -k1
  757. else
  758.     # MINIMALAFFIXES is empty;  just produce a sorted version of STRIPPEDINPUT
  759.     sort $SORTTMP "-t$flagmarker" -u -k1f,1 -k1 $STRIPPEDINPUT
  760. fi
  761. /bin/rm -rf ${TDIR}
  762. if [ "X$MUNCHMAIL" != X ]
  763. then
  764.     (
  765.     ls -ld ${TDIR}/[A-Z]*
  766.     cat ${TMPMUNCHMAIL}
  767.     ) | mail "$MUNCHMAIL"
  768.     /bin/rm -f ${TMPMUNCHMAIL}
  769. fi
  770. exit 0
  771.